#include "sc.h"
#include "scu.h"
#include "scilp.h"
#include "assert.h"

static scu_buf_t buffer[SC_MAX_BUFFERS];
static int buffers=0;
static stream_t stream[SC_MAX_STREAMS];
static int streams=0;
static int allmulti = 0;

stream_t *_getstream(void *address)
{
  int i;
  
  for (i=0; i < streams; i++)
    if (stream[i].address == address)
      return &stream[i];

  return NULL;
}

stream_t *_registerstream(void *address)
{
  int i;
  
  for (i=0; i < streams; i++)
    if (!stream[i].address)
      break;

  if (i==streams)
    streams++;
    
  stream[i].address = address;
  stream[i].multi = allmulti;
  
  return &stream[i];
}

void _unregisterstream(stream_t *s)
{
  s->address = NULL;
}

void scInit(int argc, char **argv)
{
  scIlpInit(0);
}

void scMapNextProcessor(void)
{
}

void * _scOp(int line, int parent, char *format, int operation, ...)
{
  static int opid=0;
  ilp_op_t op;
  va_list ap;
  int c;
  stream_t *s;
  void *address, *raddress=NULL;
  
  op.id = opid++;
  op.operation = operation;
  
  va_start(ap, operation);
  for (c=0; c < strlen(format); c++)
  {
    op.argument[c].id = c;
    
    switch (format[c])
    {
      case 'i':
        s = _getstream(va_arg(ap, int*));
        
        if (!s)
        {
          printf("Input stream argument %d of operation at line %d is not registered\n",
                 c, line);
          exit(1);
        }
        
        printf("[%d] ", s->buffer);
        
        op.argument[c].buffer = &buffer[s->buffer];
        op.argument[c].reader = scuBufferAddReader(op.argument[c].buffer);
        
        if (!s->multi)
          _finalize(s->address);
        break;
      case 'd':
        op.argument[c].data.d = *va_arg(ap, int*);
        
        printf("%d ", op.argument[c].data.d);
        break;
      case 'f':
        op.argument[c].data.f = *va_arg(ap, double*);
        printf("%f ", op.argument[c].data.f);
        break;
      default:
        va_arg(ap, void*);
    }
  }
  va_end(ap);

  printf("-> ");

  va_start(ap, operation);
  for (c=0; c < strlen(format); c++)
  {
    switch (format[c])
    {
      case 'I':
        address = va_arg(ap, int*);
        
        if ((s = _getstream(address)))
          _finalize(address);
          
        if (c == strlen(format)-1 && !address)
          address = raddress = (void*)opid;
        
        s = _registerstream(address);
        s->buffer = buffers++;
        
        printf("[%d] ", s->buffer);
        
        buffer[s->buffer] = scuBufferCreate(SC_BUF_SIZE, SCU_BUF_MAX_READERS, NULL);
        op.argument[c].buffer = &buffer[s->buffer];
        break;
      case 'D':
        va_arg(ap, void*);
        printf("%%d ");
        break;
      case 'F':
        va_arg(ap, void*);
        printf("%%f ");
        break;
      default:
        va_arg(ap, void*);
    }
  }
  va_end(ap);

  printf(": ");

  ilpOperationEnqueue(&op);
  
  va_start(ap, operation);
  for (c=0; c < strlen(format); c++)
  {
    switch (format[c])
    {
      case 'D':
        *va_arg(ap, int*) = op.argument[c].data.d;
        break;
      case 'F':
        *va_arg(ap, double*) = op.argument[c].data.f;
        break;
      default:
        va_arg(ap, void*);
    }
  }
  va_end(ap);

  return raddress;
}

#ifdef __cplusplus
void _scThread(char *format, char *text, void (*op)(...), ...)
#else
void _scThread(char *format, char *text, void (*op)(), ...)
#endif
{
  fprintf(stderr, "Threads not supported\n");
}

void _finalize(STREAM *address)
{
  stream_t *s = _getstream(address);

  assert(s);

  scuBufferUnlock(&buffer[s->buffer]);
  _unregisterstream(s);
}

void _block(void *address)
{
  stream_t *s = _getstream(address);  
  if (s) _finalize(address);
}

void _schedule(int make_it_run)
{
}

void _cycle(void *address)
{
}

void _multi(void *address)
{
  stream_t *s = _getstream(address);  

  assert(s);

  s->multi = 1;  
}

void _allmulti(int multiuse)
{
  allmulti = 1;
}

void _mapto(int _affinity)
{
}

void _awaitall(void)
{
}

void _setbuffersize(int size)
{
}

int scExit(void)
{
  return 0;
}

int scTrace(int type, int line, int parent, int condition)
{
  return 0;
}

int *scint(int i)
{
  int *r = (int*)malloc(sizeof(int));
  *r = i;
  return r;
}

double *scdbl(double f)
{
  double *r = (double*)malloc(sizeof(double));
  *r = f;
  return r;
}
